//Copyright 2010 Balazs Keri, Imre Keri
//
//This file is part of FireworksCreator.
//
//FireworksCreator is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.
//
//FireworksCreator is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with FireworksCreator.  If not, see <http://www.gnu.org/licenses/>.
//
#include <global.h>
#include <QFile>
#include <QMessageBox>
#include <model/emittertemplate.h>
#include <model/group.h>
#include <model/particletemplate.h>
#include <model/shooter.h>
#include <model/root.h>
#include "document.h"
#include "primitivegroup.h"
#include "uierror.h"
#include "common/identified.h"
//
const qint32 Document::data_version_1_qt_4_6 = 1;
const quint32 Document::data_check_value = 0xd6ee0102;
//
using namespace model;
Document::Document(QObject *parent)
:
   QObject(parent),
   fw_root(new Root()),
   modified(false)
{
   history_root.parent=0;
   current_operation=&history_root;
   //pending_operation=0;
}
Document::~Document()
{
   delete_contents();
}
void Document::delete_contents()
{
   fw_root->delete_contents();
   delete_op_tree_children(&history_root);
   current_operation=&history_root;
   id_map.clear();
   id_gen.reset();
}
Root *Document::get_root() const
{
   return fw_root.data();
}
double Document::get_firework_length() const
{
   return 25.0; // temp
}
const IdDictionary &Document::get_id_map() const
{
   return id_map;
}
Id Document::generate_id()
{
   return id_gen.generate();
}
void Document::map_object(Identified *p_object)
{
   Q_ASSERT(!this->id_map.contains(p_object->get_id()));
   this->id_map.insert(p_object->get_id(),p_object);
}
void Document::unmap_object(Identified *p_object)
{
   Q_ASSERT(this->id_map.value(p_object->get_id())==p_object);
   this->id_map.remove(p_object->get_id());
}
Identified *Document::lookup_object(Id p_id)
{
   QHash<Id,Identified*>::Iterator itr=this->id_map.find(p_id);
   Q_ASSERT(itr!=this->id_map.end());
   return itr.value();
}
const Identified *Document::lookup_object(Id p_id) const
{
   QHash<Id,Identified*>::ConstIterator itr=this->id_map.find(p_id);
   Q_ASSERT(itr!=this->id_map.end());
   return itr.value();
}
bool Document::get_modified() const
{
   return modified;
}
void Document::set_modified(bool m)
{
   if(this->modified!=m)
   {
      this->modified=m;
      emit modified_changed(m);
   }
}
void Document::delete_op_tree_children(HistoryTreeItem *subtree)
{
   /*for(int i=0;i<subtree->children.count();++i)
   {
      HistoryTreeItem *child=subtree->children[i];
      delete_op_tree_children(child);
      delete child->op;
      delete child;
   }*/
   subtree->children.clear();
}
bool Document::add_to_history(PrimitiveGroup *op)
{
   QSharedPointer<HistoryTreeItem> new_node(new HistoryTreeItem(op,current_operation));
   current_operation->children.prepend(new_node);
   current_operation=new_node.data();
   emit history_changed();
   this->set_modified(true);
   return true;
}
void Document::emit_pre_primitive(const Primitive &prim)
{
   emit pre_primitive(prim);
}
void Document::emit_post_primitive(const Primitive &prim)
{
   emit post_primitive_pre(const_cast<Primitive*>(&prim));
   emit post_primitive(prim);
   emit post_primitive_post(const_cast<Primitive*>(&prim));
}
void Document::emit_fail_primitive(const Primitive &prim)
{
   emit fail_primitive(prim);
}
void Document::emit_post_primitive_group(const PrimitiveGroup &p_prim_grp)
{
   emit post_primitive_group(p_prim_grp);
}
//
void Document::store_history(const HistoryTreeItem &p_tree,QDataStream &p_stream)
{
   p_stream<<qint32(p_tree.children.size());
   for(QList<QSharedPointer<HistoryTreeItem> >::const_iterator i=p_tree.children.begin();i!=p_tree.children.end();++i)
   {
      (*i)->op->store_data(p_stream);
      store_history(*((*i).data()),p_stream);
   }
}
void Document::restore_history(HistoryTreeItem &p_tree,RestoreStream &p_stream)
{
   qint32 num_children;p_stream>>num_children;
   if(num_children<0)
      throw RestoreError(tr("HistoryTreeItem::num_children is negative"));
   for(qint32 i=0;i<num_children;++i)
   {
      QSharedPointer<HistoryTreeItem> new_item(new HistoryTreeItem());
      new_item->op.reset(new PrimitiveGroup(this));
      new_item->op->restore_data(p_stream);
      restore_history(*new_item.data(),p_stream);
      new_item->parent=&p_tree;
      p_tree.children.append(new_item);
   }
}
bool Document::save(const QString &p_file_name)
{
   QFile file(p_file_name);
   if (!file.open(QIODevice::WriteOnly))
   {
      QMessageBox::warning(0,tr("Error"),tr("Failed to open or create the file '%1': %2. The document is not saved.").arg(p_file_name,file.errorString()));
      return false;
   }
   QDataStream stream(&file);
   stream<<data_version_1_qt_4_6;
   stream.setVersion(QDataStream::Qt_4_6);
   stream<<data_check_value;
   stream<<id_gen;
   stream<<qint32(fw_root->particle_templates.size());
   for(QSet<ParticleTemplate*>::const_iterator i=fw_root->particle_templates.begin();i!=fw_root->particle_templates.end();++i)
   {
      stream<<(*i)->get_id();
   }
   stream<<qint32(fw_root->emitter_templates.size());
   for(QSet<EmitterTemplate*>::const_iterator i=fw_root->emitter_templates.begin();i!=fw_root->emitter_templates.end();++i)
   {
      stream<<(*i)->get_id();
   }
   stream<<qint32(fw_root->shooters.size());
   for(QSet<Shooter*>::const_iterator i=fw_root->shooters.begin();i!=fw_root->shooters.end();++i)
   {
      stream<<(*i)->get_id();
   }
   stream<<qint32(fw_root->groups.size());
   for(QSet<Group*>::const_iterator i=fw_root->groups.begin();i!=fw_root->groups.end();++i)
   {
      stream<<(*i)->get_id();
   }
   stream<<fw_root->root_group->get_id();
   for(QSet<ParticleTemplate*>::const_iterator i=fw_root->particle_templates.begin();i!=fw_root->particle_templates.end();++i)
   {
      (*i)->store_data(stream);
   }
   for(QSet<EmitterTemplate*>::const_iterator i=fw_root->emitter_templates.begin();i!=fw_root->emitter_templates.end();++i)
   {
      (*i)->store_data(stream);
   }
   for(QSet<Shooter*>::const_iterator i=fw_root->shooters.begin();i!=fw_root->shooters.end();++i)
   {
      (*i)->store_data(stream);
   }
   for(QSet<Group*>::const_iterator i=fw_root->groups.begin();i!=fw_root->groups.end();++i)
   {
      (*i)->store_data(stream);
   }
   fw_root->root_group->store_data(stream);
   stream<<fw_root->child;
   stream<<fw_root->parent;
   store_history(history_root,stream);
   if (!file.flush())
   {
      QMessageBox::warning(0,tr("Error"),tr("Failed to write to file '%1': %2. The document is not saved.").arg(p_file_name,file.errorString()));
      file.close();
      return false;
   }
   file.close();
   return true;
}
bool Document::load(const QString &p_file_name)
{
   QFile file(p_file_name);
   if(!file.open(QIODevice::ReadOnly))
   {
      QMessageBox::warning(0,tr("Error"),tr("Failed to open the file '%1': %2.").arg(p_file_name,file.errorString()));
      return false;
   }
   RestoreStream stream(id_map,&file);
   try
   {
      qint32 tmp,i;
      stream>>tmp;
      if(tmp!= data_version_1_qt_4_6)
         throw RestoreError(tr("Bad file format version."));
      stream.setVersion(QDataStream::Qt_4_6);
      quint32 check_val;
      stream>>check_val;
      if(check_val!=data_check_value)
         throw RestoreError(tr("Bad file format."));
      stream>>id_gen;
      stream>>tmp;
      for(i=0;i<tmp;++i)
      {
         Id id;
         ParticleTemplate *new_obj=new ParticleTemplate;
         stream>>id;
         new_obj->set_id(id);
         fw_root->particle_templates.insert(new_obj);
         map_object(new_obj);
      }
      stream>>tmp;
      for(i=0;i<tmp;++i)
      {
         Id id;
         EmitterTemplate *new_obj=new EmitterTemplate;
         stream>>id;
         new_obj->set_id(id);
         fw_root->emitter_templates.insert(new_obj);
         map_object(new_obj);
      }
      //TODO
      stream>>tmp;
      for(i=0;i<tmp;++i)
      {
         Id id;
         Shooter *new_obj=0;//new Shooter;
         stream>>id;
         new_obj->set_id(id);
         fw_root->shooters.insert(new_obj);
         map_object(new_obj);
      }
      stream>>tmp;
      for(i=0;i<tmp;++i)
      {
         Id id;
         Group *new_obj=new Group;
         stream>>id;
         new_obj->set_id(id);
         fw_root->groups.insert(new_obj);
         map_object(new_obj);
      }
      {
         Id id;
         fw_root->root_group=new Group;
         stream>>id;
         fw_root->root_group->set_id(id);
         map_object(fw_root->root_group);
      }
      for(QSet<ParticleTemplate*>::iterator i=fw_root->particle_templates.begin();i!=fw_root->particle_templates.end();++i){
         (*i)->restore_data(stream);
      }
      for(QSet<EmitterTemplate*>::iterator i=fw_root->emitter_templates.begin();i!=fw_root->emitter_templates.end();++i){
         (*i)->restore_data(stream);
      }
      for(QSet<Shooter*>::iterator i=fw_root->shooters.begin();i!=fw_root->shooters.end();++i){
         (*i)->restore_data(stream);
      }
      for(QSet<Group*>::iterator i=fw_root->groups.begin();i!=fw_root->groups.end();++i){
         (*i)->restore_data(stream);
      }
      fw_root->root_group->restore_data(stream);
      stream>>fw_root->child;
      stream>>fw_root->parent;
      restore_history(history_root,stream);
   }
   catch(RestoreError &err)
   {
      QMessageBox::warning(0,tr("Error"),tr("Opening the file '%1' failed: %2.").arg(p_file_name,err.get_message()));
      file.close();
      return false;
   }
   file.close();
   emit history_changed();
   return true;
}
void Document::initialize_new()
{
   Q_ASSERT(fw_root->root_group==0);
   Id id=generate_id();
   fw_root->root_group=new Group();
   fw_root->root_group->set_id(id);
   map_object(fw_root->root_group);
   fw_root->root_group->set_name(tr("root"));
}
bool Document::undo()
{
   if(!this->current_operation->parent)
      return false;
   //
   UiError err=this->current_operation->op->undo();
   if(err.success())
   {
      this->current_operation=this->current_operation->parent;
      emit history_changed();
   }
   return err.success();
}
bool Document::redo(int i)
{
   if(i<0||i>=this->current_operation->children.count())
      return false;
   //
   HistoryTreeItem *const new_item=this->current_operation->children.at(i).data();
   UiError err=new_item->op->exec();
   if(err.success())
   {
      this->current_operation=new_item;
      emit history_changed();
   }
   return err.success();
}
QString Document::get_undo_op_description() const
{
   if(this->current_operation->parent)
   {
      return this->current_operation->op->get_description();
   }
   return QString::null;
}
QStringList Document::get_redo_op_descriptions() const
{
   QStringList ret;
   for(int i=0;i<this->current_operation->children.count();++i)
   {
      ret.append(this->current_operation->children.at(i)->op->get_description());
   }
   return ret;
}
//
Document::HistoryTreeItem::HistoryTreeItem(PrimitiveGroup *p_op,HistoryTreeItem *p_parent)
:
op(p_op),parent(p_parent) {
}
/*void Document::find_op_path(HistoryTreeItem *current,
                     HistoryTreeItem *from,
                     HistoryTreeItem *to,
                     QList<HistoryTreeItem*> &path,
                     unsigned int &state,
                     int &junction) {
   const unsigned int FOUND_FROM = 0x103;
   const unsigned int FOUND_TO = 0x104;
   const unsigned int COLLECT = 0x100;
   const unsigned int COLLECT_REV = 0x101;
   const unsigned int DONE = 0x102;

   for (int i = 0; i < current->children.count(); ++i) {
      HistoryTreeItem *child = current->children[i];
      bool found_first = false;
      if (state == 0) {
         if (child == from) {
            state = FOUND_FROM;
            found_first = true;
         } else if (child == to) {
            state = FOUND_TO;
            found_first = true;
         }
      } else if (state == FOUND_FROM && child == to) {
         state = COLLECT;
         path.append(child);
         return;
      } else if (state == FOUND_TO && child == from) {
         state = COLLECT_REV;
         path.prepend(child);
         ++junction;
         return;
      }
      find_op_path(child, from, to, path, state, junction);
      if (state != 0 && state != DONE && junction != -1 && path[junction] == child) {
         if (state == FOUND_FROM) {
            path.append(current);
            ++junction;
         } else if (state == FOUND_TO) {
            path.prepend(current);
            --junction;
         } else {
            state = DONE;
            return;
         }
      }
      if (state == COLLECT) {
         path.insert(junction + 1, child);
         if (found_first) {
            state = DONE;
            return;
         }
      } else if (state == COLLECT_REV) {
         path.insert(junction, child);
         ++junction;
         if (found_first) {
            state = DONE;
            return;
         }
      } else if (state == DONE) {
         return;
      } else if (found_first) {
         path.append(current);
         junction = 0;
      }
   }
}*/

/*bool Document::register_pending_op(Operation* p_op, OperationArgs *p_args) {
   Q_ASSERT(!pending_operation);
   pending_operation = p_op;
   pending_operation->init(this, p_args);
   return true;
}

bool Document::apply_pending_op() {
   Q_ASSERT(pending_operation);
   bool success = pending_operation->exec(this);
   if (success) {
      HistoryTreeItem *opnode = new HistoryTreeItem(pending_operation, current_operation);
      current_operation->children.append(opnode);
      current_operation = opnode;
      pending_operation = 0;
   }
   return success;
}

bool Document::undo_current_op() {
   Q_ASSERT(current_operation && current_operation->parent);
   bool success = current_operation->op->undo(this);
   if (success) {
      current_operation = current_operation->parent;
   }
   return success;
}

bool Document::set_current_op(HistoryTreeItem* p_op) {
   return false;
}*/
